// Serve - minimal Java HTTP server class
//
// Copyright (C)1996,1998 by Jef Poskanzer <jef@acme.com>. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
// Visit the ACME Labs Java page for up-to-date versions of this and other
// fine Java utilities: http://www.acme.com/java/

/* Copyright (c) 2013 fab-o-lab

 Permission is hereby granted, free of charge, to any person
 obtaining a copy of this software and associated documentation
 files (the "Software"), to deal in the Software without
 restriction, including without limitation the rights to use,
 copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the
 Software is furnished to do so, subject to the following
 conditions:

 The above copyright notice and this permission notice shall be
 included in all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 OTHER DEALINGS IN THE SOFTWARE.
*/

package com.fabolab.jetset.core;

import java.io.*;
import java.util.*;
import java.net.*;
import java.security.Principal;
import java.text.*;

import com.fabolab.Utils;
import com.fabolab.jetset.core.config.ConfigurationManager;
import com.fabolab.jetset.core.config.model.Context;
import com.fabolab.jetset.core.config.model.ServletMapping;
import com.fabolab.jetset.core.security.GenericPrincipal;
import com.fabolab.jetset.core.security.XmlRealm;
import com.fabolab.jetset.core.servlet.*;
import com.fabolab.jetset.core.servlet.http.*;

import android.app.Service;
import android.util.Log;


// @see com.fabolab.jetset.core.servlet.http.HttpServlet
// @see FileServlet
// @see CgiServlet

public class Server implements ServletContext {

	private static final String progName = "Server";

	private static final String fullProgName = "JetSet Server V0.5";
	


	
	private boolean isRunning = true;


	private int port;

	private PrintStream logStream;

	com.fabolab.WildcardDictionary registry;

	private ResourceBundle resources;

	private Context context = new Context();
	

	private Service service; 
	
	private ConfigurationManager configurationManager = new ConfigurationManager();
	
	public Context getContext() {
		return context;
	}

	public void setContext(Context context) {
		this.context = context;
	}


	/// Constructor.
	public Server(int port, PrintStream logStream, Context context,Service service) {
		this.service = service;
		this.context = context;
		this.port = context.getPort();
		this.logStream = logStream;
		registry = new com.fabolab.WildcardDictionary();
		Log.d("SRV", "before config");
		try {
			this.context.setWebApp(configurationManager.parseWebXml(service.getAssets().open("config/web.xml")));
		} catch (IOException e) {
			log("Error reading web.xml configuration.");
			e.printStackTrace();
		}
		//Set by default the XmlRealm 

		try {
			this.context.setRealm(new XmlRealm(configurationManager,service.getAssets().open("config/users.xml")));
		} catch (IOException e) {
			log("Error reading users.xml configuration.");
			e.printStackTrace();
		}

	}

	/// Constructor, default log stream.
	public Server(int port, Context context,Service service) {
		this(port, System.out, context, service);
	}

	/// Constructor, default port and log stream.
	// We don't use 80 as the default port because we don't want to
	// encourage people to run a Java web server as root because Java
	// currently has no way of giving up root privs!  Instead, the
	// current default port is 9090.
	public Server(Context context, Service service) {
		
		this(9090, System.err, context, service);
	}

	/// Register a Servlet by class name.  Registration consists of a URL
	// pattern, which can contain wildcards, and the class name of the Servlet
	// to launch when a matching URL comes in.  Patterns are checked for
	// matches in the order they were added, and only the first match is run.
	public void addServlet(String urlPat, String className) {
		log("Registering servlet: " + className + " urlPattern: " + urlPat);
		// See if we have already instantiated this one.
		Servlet servlet = (Servlet) servlets.get(className);
		if (servlet != null) {
			addServlet(urlPat, servlet);
			return;
		}

		// Check if we're allowed to make one of these.
		SecurityManager security = System.getSecurityManager();
		if (security != null) {
			int i = className.lastIndexOf('.');
			if (i != -1) {
				security.checkPackageAccess(className.substring(0, i));
				security.checkPackageDefinition(className.substring(0, i));
			}
		}

		// Make a new one.
		try {
			servlet = (Servlet) Class.forName(className).newInstance();
			log("Classname: " + servlet.getClass().getName());
			if(AndroidContextServlet.class.isAssignableFrom(servlet.getClass())){
				log("AndroidContext servlet identified");
				AndroidContextServlet acs = (AndroidContextServlet) servlet;

				acs.setContext(this.context);
				acs.setService(this.service);
				log("context"+ acs.getContext());
				log("service" + acs.getService());
				addServlet(urlPat, acs);
			}else{
				addServlet(urlPat, servlet);
			}
			return;
		} catch (ClassNotFoundException e) {
			log("Class not found: " + className);
		} catch (ClassCastException e) {
			log("Class cast problem: " + e.getMessage());
		} catch (InstantiationException e) {
			log("Instantiation problem: " + e.getMessage());
		} catch (IllegalAccessException e) {
			log("Illegal class access: " + e.getMessage());
		} catch (Exception e) {
			log("Unexpected problem creating servlet: " + e);
		}
	}

	/// Register a Servlet.  Registration consists of a URL pattern,
	// which can contain wildcards, and the Servlet to
	// launch when a matching URL comes in.  Patterns are checked for
	// matches in the order they were added, and only the first match is run.
	public void addServlet(String urlPat, Servlet servlet) {
		try {
			servlet.init(new ServeConfig((ServletContext) this));
			registry.put(urlPat, servlet);
			servlets.put(servlet.getClass().getName(), servlet);
		} catch (ServletException e) {
			log("Problem initializing servlet: " + e);
		}
	}

	/// Register a standard set of Servlets.  These will return
	// files or directory listings, and run CGI programs, much like a
	// standard HTTP server.
	// <P>
	// Because of the pattern checking order, this should be called
	// <B>after</B> you've added any custom Servlets.
	// <P>
	// The current set of default servlet mappings:
	// <UL>
	// <LI> If enabled, *.cgi goes to CgiServlet, and gets run as a CGI program.
	// <LI> * goes to FileServlet, and gets served up as a file or directory.
	// </UL>
	// @param cgi whether to run CGI programs
	public void addDefaultServlets() {
//		addServlet("*/admin.html", new com.fabolab.jetset.core.AdminServlet());
//		addServlet("*/sadmin.html", new com.fabolab.jetset.core.SecureAdminServlet());
	}

	/// Register a standard set of Servlets, with throttles.
	// @param cgi whether to run CGI programs
	// @param throttles filename to read FileServlet throttle settings from
	@Deprecated
	public void addDefaultServlets(boolean cgi, String throttles)
			throws IOException {
		if (cgi)
			addServlet("*.cgi", new com.fabolab.jetset.core.CgiServlet());
		addServlet("*/admin.html", new com.fabolab.jetset.core.AdminServlet());
		addServlet("*.wki", new com.fabolab.jetset.core.WikiServlet());
		addServlet("*", new com.fabolab.jetset.core.FileServlet(throttles));
	}
	
	public void addWebAppServlets(){
		for(ServletMapping mapping : context.getWebApp().getServletMappings()){
			Log.d("SRV", "ServletMappingname: " + mapping.getName() + " " + mapping.getUrlPattern());
			addServlet(mapping.getUrlPattern(), lookupServletClassName(mapping.getName()));
		}
	}
	
	private String lookupServletClassName(String servletName){
		boolean exit = false;
		String className = null;
		
		for(int t=0; t < context.getWebApp().getServlets().size() && !exit; t++){
			Log.d("SRV", "Servletname: " + context.getWebApp().getServlets().get(t).getName() + " " +context.getWebApp().getServlets().get(t).getClassName());
			if(context.getWebApp().getServlets().get(t).getName().equalsIgnoreCase(servletName)){
				className = context.getWebApp().getServlets().get(t).getClassName();
				exit = true;
			}
		}
		return className;
	}

	/// Run the server.  Returns only on errors.
	public void serve() {
		isRunning = true;
		ServerSocket serverSocket;
		try {
			serverSocket = new ServerSocket(port, 1000);
		} catch (IOException e) {
			log("Server socket: " + e);
			return;
		}

		try {
			while (isRunning) {
				log("socket listener on port: " + port);
				Socket socket = serverSocket.accept();
				log("request received");
				new ServeConnection(socket, this);
			}
		} catch (IOException e) {
			log("Accept: " + e);
		} finally {
			try {
				serverSocket.close();
				destroyAllServlets();
			} catch (IOException e) {
			}
		}
		log("Sserver thread stopped.");
	}

	// Methods from ServletContext.

	public boolean isRunning() {
		return isRunning;
	}

	public synchronized void setRunning(boolean isRunning) {
		this.isRunning = isRunning;
	}

	protected Hashtable servlets = new Hashtable();

	/// Gets a servlet by name.
	// @param name the servlet name
	// @return null if the servlet does not exist
	public Servlet getServlet(String name) {
		return (Servlet) servlets.get(name);
	}

	/// Enumerates the servlets in this context (server). Only servlets that
	// are accesible will be returned.  This enumeration always includes the
	// servlet itself.
	public Enumeration getServlets() {
		return servlets.elements();
	}

	/// Enumerates the names of the servlets in this context (server). Only
	// servlets that are accesible will be returned.  This enumeration always
	// includes the servlet itself.
	public Enumeration getServletNames() {
		return servlets.keys();
	}

	/// Destroys all currently-loaded servlets.
	public void destroyAllServlets() {
		Enumeration en = servlets.elements();
		while (en.hasMoreElements()) {
			Servlet servlet = (Servlet) en.nextElement();
			servlet.destroy();
		}
		servlets.clear();
	}

	/// Write information to the servlet log.
	// @param message the message to log
	public void log(String message) {
		Date date = new Date(System.currentTimeMillis());
		Log.d("SRV","[" + date.toString() + "] " + message);
	}

	/// Write a stack trace to the servlet log.
	// @param exception where to get the stack trace
	// @param message the message to log
	public void log(Exception exception, String message) {
		// !!!
		log(message);
	}

	/// Applies alias rules to the specified virtual path and returns the
	// corresponding real path.  It returns null if the translation
	// cannot be performed.
	// @param path the path to be translated
	public String getRealPath(String path) {
		// Map to document path in resources.
		return context.getDocumentPath() + File.separatorChar + path;
	}

	/// Returns the MIME type of the specified file.
	// @param file file name whose MIME type is required
	public String getMimeType(String file) {
		int lastDot = file.lastIndexOf('.');
		int lastSep = file.lastIndexOf(File.separatorChar);
		if (lastDot == -1 || (lastSep != -1 && lastDot < lastSep))
			return "text/plain; charset=iso-8859-1";
		String extension = file.substring(lastDot + 1);
		if (extension.equals("html") || extension.equals("htm"))
			return "text/html; charset=iso-8859-1";
		if (extension.equals("gif"))
			return "image/gif";
		if (extension.equals("css"))
			return "text/css";
		if (extension.equals("js"))
			return "application/x-javascript";
		if (extension.equals("jpg") || extension.equals("jpeg"))
			return "image/jpeg";
		if (extension.equals("pdf"))
			return "application/pdf";
		if (extension.equals("zip"))		
			return "application/zip";
		if (extension.equals("doc"))		
			return "application/msword";
		if (extension.equals("xls"))		
			return "application/vnd.ms-excel";
		if (extension.equals("au"))
			return "audio/basic";
		if (extension.equals("ra") || extension.equals("ram"))
			return "audio/x-pn-realaudio";
		if (extension.equals("wav"))
			return "audio/wav";
		if (extension.equals("mpg") || extension.equals("mpeg"))
			return "video/mpeg";
		if (extension.equals("qt") || extension.equals("mov"))
			return "video/quicktime";
		if (extension.equals("class"))
			return "application/octet-stream";
		if (extension.equals("ps"))
			return "application/postscript";
		if (extension.equals("wrl"))
			return "x-world/x-vrml";
		if (extension.equals("pac"))
			return "application/x-ns-proxy-autoconfig";
		return "text/plain; charset=utf-8";
	}

	/// Returns the name and version of the web server under which the servlet
	// is running.
	// Same as the CGI variable SERVER_SOFTWARE.
	public String getServerInfo() {
		return ServeUtils.serverName + " " + ServeUtils.serverVersion + " ("
				+ ServeUtils.serverUrl + ")";
	}

	/// Returns the value of the named attribute of the network service, or
	// null if the attribute does not exist.  This method allows access to
	// additional information about the service, not already provided by
	// the other methods in this interface.
	public Object getAttribute(String name) {
		// This server does not support attributes.
		return null;
	}

}

class ServeConfig implements ServletConfig {

	private ServletContext context;

	public ServeConfig(ServletContext context) {
		this.context = context;
	}

	// Methods from ServletConfig.

	/// Returns the context for the servlet.
	public ServletContext getServletContext() {
		return context;
	}

	/// Gets an initialization parameter of the servlet.
	// @param name the parameter name
	public String getInitParameter(String name) {
		// This server doesn't support servlet init params.
		return null;
	}

	/// Gets the names of the initialization parameters of the servlet.
	// @param name the parameter name
	public Enumeration getInitParameterNames() {
		// This server doesn't support servlet init params.
		return new Vector().elements();
	}

}

/**
 * @author luc
 *
 */
class ServeConnection implements Runnable, HttpServletRequest,
		HttpServletResponse {

	private Socket socket;

	private Server serve;

	private ServletInputStream in;

	private ServletOutputStream out;

	private Vector cookies = new Vector(); // !!!

	/// Constructor.
	public ServeConnection(Socket socket, Server serve) {
		// Save arguments.
		this.socket = socket;
		this.serve = serve;
		

		// Start a separate thread to read and handle the request.
		Thread thread = new Thread(this);
		thread.start();
	}

	// Methods from Runnable.

	private String reqMethod = null;

	private String reqUriPath = null;

	private String reqProtocol = null;

	private boolean oneOne; // HTTP/1.1 or better

	private boolean reqMime;

	String reqQuery = null;

	private Vector reqHeaderNames = new Vector();

	private Vector reqHeaderValues = new Vector();
	
	GenericPrincipal principal = null;
	

	public void run() {
		try {
			// Get the streams.
			in = new ServeInputStream(socket.getInputStream());
			out = new ServeOutputStream(socket.getOutputStream(), this);
		} catch (IOException e) {
			problem("Getting streams: " + e.getMessage(), SC_BAD_REQUEST);
		}

		parseRequest();

		try {
			socket.close();
		} catch (IOException e) { /* ignore */
		}
	}

	private void parseRequest() {
		byte[] lineBytes = new byte[4096];
		int len;
		String line;
		String method = "";
		int contentLength = 0;
		try {
			// Read the first line of the request.
			len = in.readLine(lineBytes, 0, lineBytes.length);
			if (len == -1 || len == 0) {
				problem("Empty request", SC_BAD_REQUEST);
				return;
			}
			line = new String(lineBytes, 0, len);
			String[] tokens = com.fabolab.Utils.splitStr(line);
			method = tokens[0];
			switch (tokens.length) {
			case 2:
				// Two tokens means the protocol is HTTP/0.9.
				reqProtocol = "HTTP/0.9";
				oneOne = false;
				reqMime = false;
				break;
			case 3:
				reqProtocol = tokens[2];
				oneOne = !reqProtocol.toUpperCase().equals("HTTP/1.0");
				reqMime = true;
				// Read the rest of the lines.
				//System.out.println("Method -> " + method);
				while (true) {
					len = in.readLine(lineBytes, 0, lineBytes.length);
					if (len == -1 || len == 0){
						if(len == 0)
						{
							if(method.equalsIgnoreCase("POST")){
								if(!this.getContentType().toLowerCase().startsWith("multipart/form-data")){
									//System.out.println("LENGTH: " + contentLength);
									len = in.readLine(lineBytes,0, contentLength);
									line = new String(lineBytes, 0, len);
									//System.out.println("PARAMS: " + line);
									reqQuery = line;
								}
								break;
							}
							else
							{
								break;
							}
						}
						break;
					}
					line = new String(lineBytes, 0, len);
//					Log.d("SRV",line);
					int colonBlank = line.indexOf(": ");
					if (colonBlank != -1) {
						String name = line.substring(0, colonBlank);
						String value = line.substring(colonBlank + 2);
						reqHeaderNames.addElement(name.toLowerCase());
						reqHeaderValues.addElement(value);
						if(name.equalsIgnoreCase("content-length")){
							contentLength = Integer.parseInt(value);
						}
					}
				}
				break;
			default:
				problem("Malformed request line", SC_BAD_REQUEST);
				return;
			}
			reqMethod = tokens[0];
			reqUriPath = tokens[1];

			// Check Host: header in HTTP/1.1 requests.
			if (oneOne) {
				String host = getHeader("host");
				if (host == null) {
					problem("Host header missing on HTTP/1.1 request",
							SC_BAD_REQUEST);
					return;
				}
				// !!!
			}

			if(method.equalsIgnoreCase("GET")){
				// Split off query string, if any.
				int qmark = reqUriPath.indexOf('?');
				if (qmark != -1) {
					reqQuery = reqUriPath.substring(qmark + 1);
					reqUriPath = reqUriPath.substring(0, qmark);
				}
				
			}

			// Decode %-sequences.
			reqUriPath = decode(reqUriPath);

			Servlet servlet = (Servlet) serve.registry.get(reqUriPath);
			if (servlet != null)
				runServlet((HttpServlet) servlet);
		} catch (IOException e) {
			problem("Reading request: " + e.getMessage(), SC_BAD_REQUEST);
		}
	}

	private void runServlet(HttpServlet servlet) {
		// Set default response fields.
		setStatus(SC_OK);
		setDateHeader("Date", System.currentTimeMillis());
		setHeader("Server", ServeUtils.serverName + "/"
				+ ServeUtils.serverVersion);
		setHeader("Connection", "close");
		//Basic authentication support
		String authenicationValue = getHeader("Authorization");
		if(authenicationValue != null){
			String[] params = authenicationValue.split(" ");
			if(params.length != 2){
				try {
					this.sendAuthenticationRequest(HttpServletResponse.SC_UNAUTHORIZED, "WWW-Authenticate", "Basic realm=\"JetSetRealm\"");
				} catch (IOException e) {
					Log.d("SRV",e.getMessage(),e);
				}
				return;
			}
			if(!params[0].equalsIgnoreCase("Basic")){
				try {
					this.sendAuthenticationRequest(HttpServletResponse.SC_UNAUTHORIZED, "WWW-Authenticate", "Basic realm=\"JetSetRealm\"");
				} catch (IOException e) {
					Log.d("SRV",e.getMessage(),e);
				}
				return;				
			}
			String decodedValue = new String(Utils.decode64(params[1]));
			String[] credentials = decodedValue.split(":");
			if(credentials.length != 2){
				try {
					this.sendAuthenticationRequest(HttpServletResponse.SC_UNAUTHORIZED, "WWW-Authenticate", "Basic realm=\"JetSetRealm\"");
				} catch (IOException e) {
					Log.d("SRV",e.getMessage(),e);
				}
				return;
			}
			this.setRemoteUser(credentials[0]);
			this.principal = (GenericPrincipal) this.serve.getContext().getRealm().authenticate(credentials[0], credentials[1]);
		}
		//End authentication
		try {
			servlet.service(this, this);
		} catch (IOException e) {
			problem("IO problem running servlet: " + e.toString(),
					SC_BAD_REQUEST);
		} catch (ServletException e) {
			problem("problem running servlet: " + e.toString(), SC_BAD_REQUEST);
		} catch (Exception e) {
			problem("unexpected problem running servlet: " + e.toString(),
					SC_INTERNAL_SERVER_ERROR);
		}
	}

	private void problem(String logMessage, int resCode) {
		serve.log(logMessage);
		try {
			sendError(resCode);
		} catch (IOException e) { /* ignore */
		}
	}

	private String decode(String str) {
		StringBuffer result = new StringBuffer();
		int l = str.length();
		for (int i = 0; i < l; ++i) {
			char c = str.charAt(i);
			if (c == '%' && i + 2 < l) {
				char c1 = str.charAt(i + 1);
				char c2 = str.charAt(i + 2);
				if (isHexit(c1) && isHexit(c2)) {
					result.append((char) (hexit(c1) * 16 + hexit(c2)));
					i += 2;
				} else
					result.append(c);
			} else
				result.append(c);
		}
		return result.toString();
	}

	private boolean isHexit(char c) {
		String legalChars = "0123456789abcdefABCDEF";
		return (legalChars.indexOf(c) != -1);
	}

	private int hexit(char c) {
		if (c >= '0' && c <= '9')
			return c - '0';
		if (c >= 'a' && c <= 'f')
			return c - 'a' + 10;
		if (c >= 'A' && c <= 'F')
			return c - 'A' + 10;
		return 0; // shouldn't happen, we're guarded by isHexit()
	}

	// Methods from ServletRequest.

	/// Returns the size of the request entity data, or -1 if not known.
	// Same as the CGI variable CONTENT_LENGTH.
	public int getContentLength() {
		return getIntHeader("content-length", -1);
	}

	/// Returns the MIME type of the request entity data, or null if
	// not known.
	// Same as the CGI variable CONTENT_TYPE.
	public String getContentType() {
		return getHeader("content-type");
	}

	/// Returns the protocol and version of the request as a string of
	// the form <protocol>/<major version>.<minor version>.
	// Same as the CGI variable SERVER_PROTOCOL.
	public String getProtocol() {
		return reqProtocol;
	}

	///  Returns the scheme of the URL used in this request, for example
	// "http", "https", or "ftp".  Different schemes have different rules
	// for constructing URLs, as noted in RFC 1738.  The URL used to create
	// a request may be reconstructed using this scheme, the server name
	// and port, and additional information such as URIs.
	public String getScheme() {
		return "http";
	}

	/// Returns the host name of the server as used in the <host> part of
	// the request URI.
	// Same as the CGI variable SERVER_NAME.
	public String getServerName() {
		try {
			return InetAddress.getLocalHost().getHostName();
		} catch (UnknownHostException e) {
			return null;
		}
	}

	/// Returns the port number on which this request was received as used in
	// the <port> part of the request URI.
	// Same as the CGI variable SERVER_PORT.
	public int getServerPort() {
		return socket.getLocalPort();
	}

	/// Returns the IP address of the agent that sent the request.
	// Same as the CGI variable REMOTE_ADDR.
	public String getRemoteAddr() {
		return socket.getInetAddress().toString();
	}

	/// Returns the fully qualified host name of the agent that sent the
	// request.
	// Same as the CGI variable REMOTE_HOST.
	public String getRemoteHost() {
		return socket.getInetAddress().getHostName();
	}

	/// Applies alias rules to the specified virtual path and returns the
	// corresponding real path, or null if the translation can not be
	// performed for any reason.  For example, an HTTP servlet would
	// resolve the path using the virtual docroot, if virtual hosting is
	// enabled, and with the default docroot otherwise.  Calling this
	// method with the string "/" as an argument returns the document root.
	public String getRealPath(String path) {
		return serve.getRealPath(path);
	}

	/// Returns an input stream for reading request data.
	// @exception IllegalStateException if getReader has already been called
	// @exception IOException on other I/O-related errors
	public ServletInputStream getInputStream() throws IOException {
		return in;
	}

	/// Returns a buffered reader for reading request data.
	// @exception UnsupportedEncodingException if the character set encoding isn't supported
	// @exception IllegalStateException if getInputStream has already been called
	// @exception IOException on other I/O-related errors
	public BufferedReader getReader() {
		// !!!
		return null;
	}

	Vector queryNames = null;

	Vector queryValues = null;

	private String remoteUser;

	public void setRemoteUser(String remoteUser) {
		this.remoteUser = remoteUser;
	}

	/// Returns the parameter names for this request.
	public Enumeration getParameterNames() {
		if (queryNames == null) {
			queryNames = new Vector();
			queryValues = new Vector();
			String qs = getQueryString();
			if (qs != null) {
				Enumeration en = new StringTokenizer(qs, "&");
				while (en.hasMoreElements()) {
					String nv = (String) en.nextElement();
					int eq = nv.indexOf('=');
					String name, value;
					if (eq == -1) {
						name = nv;
						value = "";
					} else {
						name = nv.substring(0, eq);
						value = nv.substring(eq + 1);
					}
					queryNames.addElement(name);
					queryValues.addElement(value);
				}
			}
		}
		return queryNames.elements();
	}

	/// Returns the value of the specified query string parameter, or null
	// if not found.
	// @param name the parameter name
	public String getParameter(String name) {
		Enumeration en = getParameterNames();
		int i = queryNames.indexOf(name);
		if (i == -1)
			return null;
		else
			return (String) queryValues.elementAt(i);
	}

	/// Returns the values of the specified parameter for the request as an
	// array of strings, or null if the named parameter does not exist.
	public String[] getParameterValues(String name) {
		Vector v = new Vector();
		Enumeration en = getParameterNames();
		for (int i = 0; i < queryNames.size(); ++i) {
			String n = (String) queryNames.elementAt(i);
			if (name.equals(n))
				v.addElement(queryValues.elementAt(i));
		}
		if (v.size() == 0)
			return null;
		String[] vArray = new String[v.size()];
		v.copyInto(vArray);
		return vArray;
	}

	/// Returns the value of the named attribute of the request, or null if
	// the attribute does not exist.  This method allows access to request
	// information not already provided by the other methods in this interface.
	public Object getAttribute(String name) {
		// This server does not implement attributes.
		return null;
	}

	// Methods from HttpServletRequest.

	/// Gets the array of cookies found in this request.
	public Cookie[] getCookies() {
		Cookie[] cookieArray = new Cookie[cookies.size()];
		cookies.copyInto(cookieArray);
		return cookieArray;
	}

	/// Returns the method with which the request was made. This can be "GET",
	// "HEAD", "POST", or an extension method.
	// Same as the CGI variable REQUEST_METHOD.
	public String getMethod() {
		return reqMethod;
	}

	/// Returns the full request URI.
	public String getRequestURI() {
		String portPart = "";
		int port = getServerPort();
		if (port != 80)
			portPart = ":" + port;
		String queryPart = "";
		String queryString = getQueryString();
		if (queryString != null && queryString.length() > 0)
			queryPart = "?" + queryString;
		return "http://" + getServerName() + portPart + reqUriPath + queryPart;
	}

	/// Returns the part of the request URI that referred to the servlet being
	// invoked.
	// Analogous to the CGI variable SCRIPT_NAME.
	public String getServletPath() {
		// In this server, the entire path is regexp-matched against the
		// servlet pattern, so there's no good way to distinguish which
		// part refers to the servlet.
		return reqUriPath;
	}

	/// Returns optional extra path information following the servlet path, but
	// immediately preceding the query string.  Returns null if not specified.
	// Same as the CGI variable PATH_INFO.
	public String getPathInfo() {
		// In this server, the entire path is regexp-matched against the
		// servlet pattern, so there's no good way to distinguish which
		// part refers to the servlet.
		return null;
	}

	/// Returns extra path information translated to a real path.  Returns
	// null if no extra path information was specified.
	// Same as the CGI variable PATH_TRANSLATED.
	public String getPathTranslated() {
		// In this server, the entire path is regexp-matched against the
		// servlet pattern, so there's no good way to distinguish which
		// part refers to the servlet.
		return null;
	}

	/// Returns the query string part of the servlet URI, or null if not known.
	// Same as the CGI variable QUERY_STRING.
	public String getQueryString() {
		return reqQuery;
	}

	/// Returns the name of the user making this request, or null if not known.
	// Same as the CGI variable REMOTE_USER.
	public String getRemoteUser() {
		
		return this.remoteUser;
	}

	/// Returns the authentication scheme of the request, or null if none.
	// Same as the CGI variable AUTH_TYPE.
	public String getAuthType() {
		// This server does not support authentication.
		return null;
	}

	/// Returns the value of a header field, or null if not known.
	// Same as the information passed in the CGI variabled HTTP_*.
	// @param name the header field name
	public String getHeader(String name) {
		int i = reqHeaderNames.indexOf(name.toLowerCase());
		if (i == -1)
			return null;
		return (String) reqHeaderValues.elementAt(i);
	}

	/// Returns the value of an integer header field.
	// @param name the header field name
	// @param def the integer value to return if header not found or invalid
	public int getIntHeader(String name, int def) {
		String val = getHeader(name);
		if (val == null)
			return def;
		try {
			return Integer.parseInt(val);
		} catch (Exception e) {
			return def;
		}
	}

	/// Returns the value of a long header field.
	// @param name the header field name
	// @param def the long value to return if header not found or invalid
	public long getLongHeader(String name, long def) {
		String val = getHeader(name);
		if (val == null)
			return def;
		try {
			return Long.parseLong(val);
		} catch (Exception e) {
			return def;
		}
	}

	/// Returns the value of a date header field.
	// @param name the header field name
	// @param def the date value to return if header not found or invalid
	public long getDateHeader(String name, long def) {
		String val = getHeader(name);
		if (val == null)
			return def;
		try {
			return DateFormat.getDateInstance().parse(val).getTime();
		} catch (Exception e) {
			return def;
		}
	}

	/// Returns an Enumeration of the header names.
	public Enumeration getHeaderNames() {
		return reqHeaderNames.elements();
	}

	// Session stuff.  Not implemented, but the API is here for compatibility.

	/// Gets the current valid session associated with this request, if
	// create is false or, if necessary, creates a new session for the
	// request, if create is true.
	// <P>
	// Note: to ensure the session is properly maintained, the servlet
	// developer must call this method (at least once) before any output
	// is written to the response.
	// <P>
	// Additionally, application-writers need to be aware that newly
	// created sessions (that is, sessions for which HttpSession.isNew
	// returns true) do not have any application-specific state.
	public HttpSession getSession(boolean create) {
		return null;
	}

	/// Gets the session id specified with this request. This may differ
	// from the actual session id.  For example, if the request specified
	// an id for an invalid session, then this will get a new session with
	// a new id.
	public String getRequestedSessionId() {
		return null;
	}

	/// Checks whether this request is associated with a session that is
	// valid in the current session context.  If it is not valid, the
	// requested session will never be returned from the getSession
	// method.
	public boolean isRequestedSessionIdValid() {
		return false;
	}

	/// Checks whether the session id specified by this request came in as
	// a cookie.  (The requested session may not be one returned by the
	// getSession method.)
	public boolean isRequestedSessionIdFromCookie() {
		return false;
	}

	/// Checks whether the session id specified by this request came in as
	// part of the URL.  (The requested session may not be the one returned
	// by the getSession method.)
	public boolean isRequestedSessionIdFromUrl() {
		return false;
	}

	// Methods from ServletResponse.

	/// Sets the content length for this response.
	// @param length the content length
	public void setContentLength(int length) {
		setIntHeader("Content-Length", length);
	}

	/// Sets the content type for this response.
	// @param type the content type
	public void setContentType(String type) {
		setHeader("Content-Type", type);
	}

	/// Returns an output stream for writing response data.
	public ServletOutputStream getOutputStream() {
		return out;
	}

	/// Returns a print writer for writing response data.  The MIME type of
	// the response will be modified, if necessary, to reflect the character
	// encoding used, through the charset=... property.  This means that the
	// content type must be set before calling this method.
	// @exception UnsupportedEncodingException if no such encoding can be provided
	// @exception IllegalStateException if getOutputStream has been called
	// @exception IOException on other I/O errors
	public PrintWriter getWriter() throws IOException {
		// !!!
		return null;
	}

	/// Returns the character set encoding used for this MIME body.  The
	// character encoding is either the one specified in the assigned
	// content type, or one which the client understands.  If no content
	// type has yet been assigned, it is implicitly set to text/plain.
	public String getCharacterEncoding() {
		// !!!
		return null;
	}

	// Methods from HttpServletResponse.

	/// Adds the specified cookie to the response.  It can be called
	// multiple times to set more than one cookie.
	public void addCookie(Cookie cookie) {
		cookies.addElement(cookie);
	}

	/// Checks whether the response message header has a field with the
	// specified name.
	public boolean containsHeader(String name) {
		return resHeaderNames.contains(name);
	}

	private int resCode = -1;

	private String resMessage = null;

	private Vector resHeaderNames = new Vector();

	private Vector resHeaderValues = new Vector();

	/// Sets the status code and message for this response.
	// @param resCode the status code
	// @param resMessage the status message
	public void setStatus(int resCode, String resMessage) {
		this.resCode = resCode;
		this.resMessage = resMessage;
	}

	/// Sets the status code and a default message for this response.
	// @param resCode the status code
	public void setStatus(int resCode) {
		switch (resCode) {
		case SC_CONTINUE:
			setStatus(resCode, "Continue");
			break;
		case SC_SWITCHING_PROTOCOLS:
			setStatus(resCode, "Switching protocols");
			break;
		case SC_OK:
			setStatus(resCode, "Ok");
			break;
		case SC_CREATED:
			setStatus(resCode, "Created");
			break;
		case SC_ACCEPTED:
			setStatus(resCode, "Accepted");
			break;
		case SC_NON_AUTHORITATIVE_INFORMATION:
			setStatus(resCode, "Non-authoritative");
			break;
		case SC_NO_CONTENT:
			setStatus(resCode, "No content");
			break;
		case SC_RESET_CONTENT:
			setStatus(resCode, "Reset content");
			break;
		case SC_PARTIAL_CONTENT:
			setStatus(resCode, "Partial content");
			break;
		case SC_MULTIPLE_CHOICES:
			setStatus(resCode, "Multiple choices");
			break;
		case SC_MOVED_PERMANENTLY:
			setStatus(resCode, "Moved permanentently");
			break;
		case SC_MOVED_TEMPORARILY:
			setStatus(resCode, "Moved temporarily");
			break;
		case SC_SEE_OTHER:
			setStatus(resCode, "See other");
			break;
		case SC_NOT_MODIFIED:
			setStatus(resCode, "Not modified");
			break;
		case SC_USE_PROXY:
			setStatus(resCode, "Use proxy");
			break;
		case SC_BAD_REQUEST:
			setStatus(resCode, "Bad request");
			break;
		case SC_UNAUTHORIZED:
			setStatus(resCode, "Unauthorized");
			break;
		case SC_PAYMENT_REQUIRED:
			setStatus(resCode, "Payment required");
			break;
		case SC_FORBIDDEN:
			setStatus(resCode, "Forbidden");
			break;
		case SC_NOT_FOUND:
			setStatus(resCode, "Not found");
			break;
		case SC_METHOD_NOT_ALLOWED:
			setStatus(resCode, "Method not allowed");
			break;
		case SC_NOT_ACCEPTABLE:
			setStatus(resCode, "Not acceptable");
			break;
		case SC_PROXY_AUTHENTICATION_REQUIRED:
			setStatus(resCode, "Proxy auth required");
			break;
		case SC_REQUEST_TIMEOUT:
			setStatus(resCode, "Request timeout");
			break;
		case SC_CONFLICT:
			setStatus(resCode, "Conflict");
			break;
		case SC_GONE:
			setStatus(resCode, "Gone");
			break;
		case SC_LENGTH_REQUIRED:
			setStatus(resCode, "Length required");
			break;
		case SC_PRECONDITION_FAILED:
			setStatus(resCode, "Precondition failed");
			break;
		case SC_REQUEST_ENTITY_TOO_LARGE:
			setStatus(resCode, "Request entity too large");
			break;
		case SC_REQUEST_URI_TOO_LONG:
			setStatus(resCode, "Request URI too large");
			break;
		case SC_UNSUPPORTED_MEDIA_TYPE:
			setStatus(resCode, "Unsupported media type");
			break;
		case SC_INTERNAL_SERVER_ERROR:
			setStatus(resCode, "Internal server error");
			break;
		case SC_NOT_IMPLEMENTED:
			setStatus(resCode, "Not implemented");
			break;
		case SC_BAD_GATEWAY:
			setStatus(resCode, "Bad gateway");
			break;
		case SC_SERVICE_UNAVAILABLE:
			setStatus(resCode, "Service unavailable");
			break;
		case SC_GATEWAY_TIMEOUT:
			setStatus(resCode, "Gateway timeout");
			break;
		case SC_HTTP_VERSION_NOT_SUPPORTED:
			setStatus(resCode, "HTTP version not supported");
			break;
		default:
			setStatus(resCode, "");
			break;
		}
	}

	/// Sets the value of a header field.
	// @param name the header field name
	// @param value the header field value
	public void setHeader(String name, String value) {
		resHeaderNames.addElement(name);
		resHeaderValues.addElement(value);
	}

	/// Sets the value of an integer header field.
	// @param name the header field name
	// @param value the header field integer value
	public void setIntHeader(String name, int value) {
		setHeader(name, Integer.toString(value));
	}

	/// Sets the value of a long header field.
	// @param name the header field name
	// @param value the header field long value
	public void setLongHeader(String name, long value) {
		setHeader(name, Long.toString(value));
	}

	/// Sets the value of a date header field.
	// @param name the header field name
	// @param value the header field date value
	public void setDateHeader(String name, long value) {
		setHeader(name, to1123String(new Date(value)));
	}

	private static final String[] weekdays = { "Sun", "Mon", "Tue", "Wed",
			"Thu", "Fri", "Sat" };

	/// Converts a Date into an RFC-1123 string.
	private static String to1123String(Date date) {
		// We have to go through some machinations here to get the
		// correct day of the week in GMT.  getDay() gives the day in
		// local time.  getDate() gives the day of the month in local
		// time.  toGMTString() gives a formatted string in GMT.  So, we
		// extract the day of the month from the GMT string, and if it
		// doesn't match the local one we change the local day of the
		// week accordingly.
		//
		// The Date class sucks.
		int localDay = date.getDay();
		int localDate = date.getDate();
		String gmtStr = date.toGMTString();
		int blank = gmtStr.indexOf(' ');
		int gmtDate = Integer.parseInt(gmtStr.substring(0, blank));
		int gmtDay;
		if (gmtDate > localDate || (gmtDate < localDate && gmtDate == 1))
			gmtDay = (localDay + 1) % 7;
		else if (localDate > gmtDate || (localDate < gmtDate && localDate == 1))
			gmtDay = (localDay + 6) % 7;
		else
			gmtDay = localDay;
		return weekdays[gmtDay] + (gmtDate < 10 ? ", 0" : ", ") + gmtStr;
	}

	private boolean headersWritten = false;

	/// Writes the status line and message headers for this response to the
	// output stream.
	// @exception IOException if an I/O error has occurred
	void writeHeaders() throws IOException {
		if (headersWritten)
			return;
		headersWritten = true;
		if (reqMime) {
			out.println(reqProtocol + " " + resCode + " " + resMessage);
			for (int i = 0; i < resHeaderNames.size(); ++i) {
				String name = (String) resHeaderNames.elementAt(i);
				String value = (String) resHeaderValues.elementAt(i);
				if (value != null) // just in case
					out.println(name + ": " + value);
			}
			out.println("");
			out.flush();
		}
	}

	/* Writes an error response using the specified status code and message.
	 * @param resCode the status code
	 * @param resMessage the status message
	 * @exception IOException if an I/O error has occurred
	 */
	public void sendError(int resCode, String resMessage) throws IOException {
		setStatus(resCode, resMessage);
		realSendError();
	}

	/* Writes an error response using the specified status code and a default
	 * message.
	 * @param resCode the status code
	 * @exception IOException if an I/O error has occurred
	 */
	public void sendError(int resCode) throws IOException {
		setStatus(resCode);
		realSendError();
	}
	
	/* (non-Javadoc)
	 * @see com.fabolab.jetset.core.servlet.http.HttpServletResponse#sendAuthenticationRequest(int, java.lang.String, java.lang.String)
	 */
	public void sendAuthenticationRequest( int resCode, String headerName, String headerValue) throws IOException{
		setStatus(resCode, resMessage);
		setHeader(headerName, headerValue);
		realSendError();
	}

	private void realSendError() throws IOException {
		setContentType("text/html");
		out.println("<!DOCTYPE html>");
		out.println("<html lang=\"en\">");
		out.println("<head>");
		out.println("<meta charset=\"utf-8\">");
		out.println("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">");
		out.println("<link href=\"/core/css/bootstrap.css\" rel=\"stylesheet\">");
		out.println("<style type=\"text/css\">");
		out.println("body {");
		out.println("	padding-top: 20px;");
		out.println("	padding-bottom: 40px;");
		out.println("}");
		out.println("</style>");
		out.println("<link href=\"/core/css/bootstrap-responsive.css\" rel=\"stylesheet\">");
		out.println("<title>JetSet "+ resCode + " Error</title>");
		out.println("</head>");
		out.println("<body>");
		out.println("<div class=\"container\"><!-- start container -->");
		out.println("<h1>JetSet Webserver</h1>");
		out.println( "<h2>" + resCode + " " + resMessage + "</h2>");
		String ua = getHeader("user-agent");
		if (ua != null && com.fabolab.Utils.match("*MSIE*", ua)) {
			out.println("<!--");
			for (int i = 0; i < 6; ++i)
				out
						.println("Padding so that MSIE deigns to show this error instead of its own canned one.");
			out.println("-->");
		}
		out.println("<hr>");
		ServeUtils.writeAddress(out);
		out.println("</div><!-- end container-->");
		out.println("</body></html>");
		out.flush();
	}

	/// Sends a redirect message to the client using the specified redirect
	// location URL.
	// @param location the redirect location URL
	// @exception IOException if an I/O error has occurred
	public void sendRedirect(String location) throws IOException {
		setHeader("Location", location);
		sendError(SC_MOVED_TEMPORARILY);
	}

	// URL session-encoding stuff.  Not implemented, but the API is here
	// for compatibility.

	/// Encodes the specified URL by including the session ID in it, or, if
	// encoding is not needed, returns the URL unchanged. The
	// implementation of this method should include the logic to determine
	// whether the session ID needs to be encoded in the URL. For example,
	// if the browser supports cookies, or session tracking is turned off,
	// URL encoding is unnecessary.
	// <P>
	// All URLs emitted by a Servlet should be run through this method.
	// Otherwise, URL rewriting cannot be used with browsers which do not
	// support cookies.
	public String encodeUrl(String url) {
		return url;
	}

	/// Encodes the specified URL for use in the sendRedirect method or, if
	// encoding is not needed, returns the URL unchanged. The
	// implementation of this method should include the logic to determine
	// whether the session ID needs to be encoded in the URL.  Because the
	// rules for making this determination differ from those used to
	// decide whether to encode a normal link, this method is seperate
	// from the encodeUrl method.
	// <P>
	// All URLs sent to the HttpServletResponse.sendRedirect method should be
	// run through this method.  Otherwise, URL rewriting cannot be used with
	// browsers which do not support cookies.
	public String encodeRedirectUrl(String url) {
		return url;
	}

	public boolean isUserInRole(String role) {
		return this.serve.getContext().getRealm().hasRole(getUserPrincipal(), role);
	}

	public Principal getUserPrincipal() {
		// TODO Auto-generated method stub
		return principal;
	}

}

class ServeInputStream extends ServletInputStream {

	private InputStream in;

	public ServeInputStream(InputStream in) {
		this.in = in;
	}

	public int readLine(byte[] b, int off, int len) throws IOException {
		int off2 = off;
		while (off2 - off < len) {
			int r = read();
			if (r == -1) {
				if (off2 == off)
					return -1;
				break;
			}
			if (r == 13)
				continue;
			if (r == 10)
				break;
			b[off2] = (byte) r;
			++off2;
		}
		return off2 - off;
	}
	
	public int readLineRaw(byte[] b, int off, int len) throws IOException {
		int off2 = off;
		while (off2 - off < len) {
			int r = read();
			if (r == -1) {
				if (off2 == off)
					return -1;
				break;
			}

			b[off2] = (byte) r;
			++off2;
			if (r == 10)
				break;
		}
		return off2 - off;		
	}

	public int read() throws IOException {
		return in.read();
	}

	public int read(byte[] b, int off, int len) throws IOException {
		return in.read(b, off, len);
	}

	public int available() throws IOException {
		return in.available();
	}

	public void close() throws IOException {
		in.close();
	}

}

class ServeOutputStream extends ServletOutputStream {

	private PrintStream out;

	private ServeConnection conn;

	public ServeOutputStream(OutputStream out, ServeConnection conn) {
		this.out = new PrintStream(out);
		this.conn = conn;
	}

	public void write(int b) throws IOException {
		conn.writeHeaders();
		out.write(b);
	}

	public void write(byte[] b, int off, int len) throws IOException {
		conn.writeHeaders();
		out.write(b, off, len);
	}

	public void flush() throws IOException {
		conn.writeHeaders();
		out.flush();
	}

	public void close() throws IOException {
		conn.writeHeaders();
		out.close();
	}

	public void print(String s) throws IOException {
		conn.writeHeaders();
		out.print(s);
	}

	public void print(int i) throws IOException {
		conn.writeHeaders();
		out.print(i);
	}

	public void print(long l) throws IOException {
		conn.writeHeaders();
		out.print(l);
	}

	public void println(String s) throws IOException {
		conn.writeHeaders();
		out.println(s);
	}

	public void println(int i) throws IOException {
		conn.writeHeaders();
		out.println(i);
	}

	public void println(long l) throws IOException {
		conn.writeHeaders();
		out.println(l);
	}

	public void println() throws IOException {
		conn.writeHeaders();
		out.println();
	}

}
